GCD源码分析1 —— 开篇

前言

在iOS的日常编码中,GCD很好地隔离了我们与线程(pthread)之间的直接交流,跟我们打交道的只有队列和Block,时间久了甚至感觉不到线程的存在。一直想找机会好好研究一下GCD底层,领略下其博大精深之要义.经过初期调研发现:GCD源码分析资料稀少,且比较零散,很难全面地对GCD进行了解,原因可能是这方面的业务需求驱动不是特别强烈,现有GCD的API足够解决所有的业务需求。但是这仍然阻止不了我对GCD底层原理了解的一腔热血,冲动之下决定给自己挖个坑,写一个GCD源码分析系列,阐述下自己对GCD底层原理的了解和理解。

GCD版本选择

接下来就是选择要分析的GCD源码版本,纵向对比了下,发现越到后期的版本,工程越庞大,文件数激增,并且源码风格越来越不友好,里面充斥了大量的宏编译选项,阅读起来很费神,这对了解底层机制十分不利。通过各个版本对比之下,最终选定了版本 libdispatch-84.5.5


这就是这个版本的所有工程文件!但是麻雀虽小,五脏俱全,它仍然包含了所有的GCD的所有内容。

前期准备

在阅读GCD源码之前,需要对一些相关的基础知识做下了解

原子操作

在分析源码之前,先来了解一些关于原子操作的代码,不需要记住,只需要有个印象,回头查询

1
2
3
4
5
6
7
8
#define dispatch_atomic_xchg(p, n)  __sync_lock_test_and_set((p), (n))
#define dispatch_atomic_cmpxchg(p, o, n) __sync_bool_compare_and_swap((p), (o), (n))
#define dispatch_atomic_inc(p) __sync_add_and_fetch((p), 1)
#define dispatch_atomic_dec(p) __sync_sub_and_fetch((p), 1)
#define dispatch_atomic_add(p, v) __sync_add_and_fetch((p), (v))
#define dispatch_atomic_sub(p, v) __sync_sub_and_fetch((p), (v))
#define dispatch_atomic_or(p, v) __sync_fetch_and_or((p), (v))
#define dispatch_atomic_and(p, v) __sync_fetch_and_and((p), (v))

  • __sync_lock_test_and_set((p), (n)) 将*p设为value并返回*p操作之前的值。
  • __sync_bool_compare_and_swap((p), (o), (n))这两个函数提供原子的比较和交换:如果*p == o,就将n写入*p(p代表地址,o代表oldValue,n代表newValue)
  • __sync_add_and_fetch((p), 1) 先自加1,再返回
  • __sync_sub_and_fetch((p), 1) 先自减1,再返回
  • __sync_add_and_fetch((p), (v)) 先自加v,再返回
  • __sync_sub_and_fetch((p), (v)) 先自减v,再返回
  • __sync_fetch_and_or((p), (v)) 先返回,再进行或运算
  • __sync_fetch_and_and((p), (v))先返回,再进行与运算

__builtin_expect

1
long __builtin_expect (long EXP, long C)

处理器一般采用流水线模式,有些里面有多个逻辑运算单元,系统可以提前取多条指令进行并行处理,但遇到跳转时,则需要重新取指令,这相对于不用重新去指令就降低了速度。然而由于程序员在分支预测方面做得很糟糕,所以用这个内建函数__builtin_expect来帮助程序员处理分支预测,优化程序。其第一个参数EXP为一个整型表达式,这个内建函数的返回值也是这个EXP,而C为一个编译期常量,这个函数的语义是:我期望EXP表达式的值等于常量C,编译器应当根据我提供的期望值进行优化。例如:

1
2
3
4
if (__builtin_expect(x, 0))  
return 1;
else
return 2;

很明显,x的期望值为0,也就是x有很大的概率为0,所以走if分支的可能性较小,所以编译器会这样编译:

1
2
3
4
if (!x)  
return 2;
else
return 1;

这样,每次CPU都能大概率地执行到预取的编译后的if分支,从而提高了分支预测的准确性,进而提升了CPU指令的执行速度。

fastpath && slowpath

1
2
#define fastpath(x) ((typeof(x))__builtin_expect((long)(x), ~0l))
#define slowpath(x) ((typeof(x))__builtin_expect((long)(x), 0l))

有了__builtin_expect基础,苹果引入的fastpathslowpath这两个宏就不难理解了:

  • fastpath 表示该条件多数情况下会发生。
  • slowpath 表示你可以确认该条件是极少发生的。

DISPATCH_DECL

1
#define DISPATCH_DECL(name) typedef struct name##_s *name##_t

GCD源码中很多地方都可以看到这个宏,举个栗子:

1
DISPATCH_DECL(dispatch_queue);

宏展开为:

1
typedef struct dispatch_queue_s *dispatch_queue_t

dispatch_queue_t就可以用来定义一个dispatch_queue_s结构体的指针。

基本数据结构

dispatch_object_t

1
2
3
4
5
6
7
8
9
10
typedef union {
struct dispatch_object_s *_do; // dispatch_object_s结构体,这个是 GCD 的基类
struct dispatch_continuation_s *_dc; // 任务类型,通常 dispatch_async内的block最终都会封装成这个数据类型
struct dispatch_queue_s *_dq; // 任务队列,我们创建的对列都是这个类型的,不管是串行队列还是并发队列
struct dispatch_queue_attr_s *_dqa; // 任务队列的属性,任务队列的属性里面包含了任务队列里面的一些操作函数,可以表明这个任务队列是串行还是并发队列
struct dispatch_group_s *_dg; // GCD的group
struct dispatch_source_s *_ds; // GCD的sourece ,可以监测内核事件,文件读写事件和 socket 通信事件等
struct dispatch_source_attr_s *_dsa; // sourece的属性。
struct dispatch_semaphore_s *_dsema; // 信号量,如果了解过 pthread 都知道,信号量可以用来调度线程
} dispatch_object_t __attribute__((transparent_union));

可以看出,dispatch_object_t是一个联合体,所以当用dispatch_object_t 可以代表这个联合体内的所有数据类型。

注意__attribute__((transparent_union))是一种典型的 透明联合体
透明联合类型削弱了C语言的类型检测机制。起到了类似强制类型转换的效果,通过透明联合体,使得dispatch_object_s就像C++中的基类,在函数参数类型的转换上,给C语言的参数传递带来极大的方便。
考虑到在底层,类型实质上是不存在的,因此所谓的透明联合类型,也就是在一定程度上打破了类型对我们的束缚,使数据以一种更底层的角度呈现在我们面前。不过这样也弱化了C语言对类型的检测,由此也可能带来一些很严重的错误。

dispatch_object_s

1
2
3
4
5
6
7
8
9
10
11
12
13
struct dispatch_object_s {
DISPATCH_STRUCT_HEADER(dispatch_object_s, dispatch_object_vtable_s);
};

#define DISPATCH_STRUCT_HEADER(x, y) \
const struct y *do_vtable; \ // 这个结构体内包含了这个 dispatch_object_s 的操作函数
struct x *volatile do_next; \ // 链表的 next
unsigned int do_ref_cnt; \ // 引用计数
unsigned int do_xref_cnt; \ // 外部引用计数
unsigned int do_suspend_cnt; \ // suspend计数,用作暂停标志,比如延时处理的任务,设置该引用计数之后;在任务到时后,计时器处理将会将该标志位修改,然后唤醒队列调度
struct dispatch_queue_s *do_targetq;\ // 目标队列,就是当前这个struct x在哪个队列运行
void *do_ctxt; \ // 上下文,我们要传递的参数
void *do_finalizer

dispatch_object_s结构体是整个GCD的基类,地位十分重要,里面的一些成员已经在注释中做了基本的解释,随着剖析的逐步深入,我们会逐步了解这些成员存在的意义。

可以看出 do_vtable 的类型是dispatch_object_vtable_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct dispatch_object_vtable_s {
DISPATCH_VTABLE_HEADER(dispatch_object_s);
};

#define DISPATCH_VTABLE_HEADER(x) \
unsigned long const do_type; \ // 数据的具体类型
const char *const do_kind; \ // 数据的类型描述字符串
size_t (*const do_debug)(struct x *, char *, size_t); \ // 用来获取调试时需要的变量信息
struct dispatch_queue_s *(*const do_invoke)(struct x *);\ // 唤醒队列的方法,全局队列和主队列此项为NULL
bool (*const do_probe)(struct x *); \ // 用于检测传入对象中的一些值是否满足条件
void (*const do_dispose)(struct x *) // 销毁队列的方法,通常内部会调用 这个对象的 finalizer 函数

#define dx_type(x) (x)->do_vtable->do_type
#define dx_kind(x) (x)->do_vtable->do_kind
#define dx_debug(x, y, z) (x)->do_vtable->do_debug((x), (y), (z))
#define dx_dispose(x) (x)->do_vtable->do_dispose(x)
#define dx_invoke(x) (x)->do_vtable->do_invoke(x)
#define dx_probe(x) (x)->do_vtable->do_probe(x)

这个结构体内包含了这个dispatch_object_s 的操作函数,而且针对这些操作函数,定义了相对简短的宏,方便调用

dispatch_continuation_s

该结构体主要用来封装block和function

1
2
3
4
5
6
7
8
9
10
11
struct dispatch_continuation_s {
DISPATCH_CONTINUATION_HEADER(dispatch_continuation_s);
dispatch_group_t dc_group;
void * dc_data[3];
};

#define DISPATCH_CONTINUATION_HEADER(x) \
const void * do_vtable; \
struct x *volatile do_next; \
dispatch_function_t dc_func; \
void * dc_ctxt

dispatch_continuation_s 代表任务类型,通常 dispatch_async内的block最终都会封装成这个数据类型

If dc_vtable is less than 127, then the object is a continuation.
Otherwise, the object has a private layout and memory management rules. The
first two words must align with normal objects.

dispatch_queue_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define DISPATCH_QUEUE_MIN_LABEL_SIZE   64

#define DISPATCH_QUEUE_HEADER \
uint32_t dq_running; \
uint32_t dq_width; \
struct dispatch_object_s *dq_items_tail; \
struct dispatch_object_s *volatile dq_items_head; \
unsigned long dq_serialnum; \
void *dq_finalizer_ctxt; \
dispatch_queue_finalizer_function_t dq_finalizer_func

struct dispatch_queue_s {
DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s);
DISPATCH_QUEUE_HEADER;
char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE]; // must be last
};

可以看出,dispatch_queue_sdispatch_object_s 差别在于 dispatch_queue_s 多了DISPATCH_QUEUE_HEADERdq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE],我们从中也可以看出,我们队列起名要少于64个字符

dispatch_group_s

1
2
3
DISPATCH_DECL(dispatch_group);

#define DISPATCH_DECL(name) typedef struct name##_s *name##_t;

可以看出,dispatch_group_t 等效于 dispatch_group_s*,那么dispatch_group_s究竟本质是什么呢?当我去找dispatch_group_s的定义时,无论如何也找不到,终究还是不能获悉dispatch_group_s的真正结构,看来只有另辟蹊径。

在看源码的过程中,在semaphore.c文件中的dispatch_group_create函数定义中发现了一些端倪

1
2
3
4
5
dispatch_group_t
dispatch_group_create(void)
{
return (dispatch_group_t)dispatch_semaphore_create(LONG_MAX);
}

结果令人十分诧异:
dispatch_group_t本质上竟然是一个value为LONG_MAX的semaphore !!!

dispatch_source_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct dispatch_source_s {
DISPATCH_STRUCT_HEADER(dispatch_source_s, dispatch_source_vtable_s);
DISPATCH_QUEUE_HEADER;
// Instruments always copies DISPATCH_QUEUE_MIN_LABEL_SIZE, which is 64,
// so the remainder of the structure must be big enough
union {
char _ds_pad[DISPATCH_QUEUE_MIN_LABEL_SIZE];
struct {
char dq_label[8];
dispatch_kevent_t ds_dkev;

dispatch_source_handler_function_t ds_handler_func;
void *ds_handler_ctxt;

void *ds_cancel_handler;

unsigned int ds_is_level:1,
ds_is_adder:1,
ds_is_installed:1,
ds_needs_rearm:1,
ds_is_armed:1,
ds_is_legacy:1,
ds_cancel_is_block:1,
ds_handler_is_block:1;

unsigned int ds_atomic_flags;

unsigned long ds_data;
unsigned long ds_pending_data;
unsigned long ds_pending_data_mask;

TAILQ_ENTRY(dispatch_source_s) ds_list;

unsigned long ds_ident_hack;

struct dispatch_timer_source_s ds_timer;
};
};
};

dispatch_semaphore_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct dispatch_semaphore_s {
DISPATCH_STRUCT_HEADER(dispatch_semaphore_s, dispatch_semaphore_vtable_s);
long dsema_value; // 信号值
long dsema_orig; // 记录其初始值,或者成为原始值,这是创建信号时设置的

// 只是一个防止信号量被意外唤醒的变量,这个变量,在signal过程中+1,然后wait的时候如果发现这个值不是1,
// 那么直接就返回了,而不会进入接下来的sim_wait
size_t dsema_sent_ksignals;

semaphore_t dsema_port;
semaphore_t dsema_waiter_port;
size_t dsema_group_waiters;
struct dispatch_sema_notify_s *dsema_notify_head;
struct dispatch_sema_notify_s *dsema_notify_tail;
};

UML图

libdispatch初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void libdispatch_init(void)
{
// 这些assert就不说了,保证定义的宏不出问题
dispatch_assert(DISPATCH_QUEUE_PRIORITY_COUNT == 3);
dispatch_assert(DISPATCH_ROOT_QUEUE_COUNT == 6);

dispatch_assert(DISPATCH_QUEUE_PRIORITY_LOW == -DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_assert(countof(_dispatch_root_queues) == DISPATCH_ROOT_QUEUE_COUNT);
dispatch_assert(countof(_dispatch_thread_mediator) == DISPATCH_ROOT_QUEUE_COUNT);
dispatch_assert(countof(_dispatch_root_queue_contexts) == DISPATCH_ROOT_QUEUE_COUNT);

// TSD池分配
_dispatch_thread_key_init_np(dispatch_queue_key, _dispatch_queue_cleanup);
_dispatch_thread_key_init_np(dispatch_sema4_key, (void (*)(void *))dispatch_release); // use the extern release
_dispatch_thread_key_init_np(dispatch_cache_key, _dispatch_cache_cleanup2);
#if DISPATCH_PERF_MON
_dispatch_thread_key_init_np(dispatch_bcounter_key, NULL);
#endif

// 主线程设置TSD
_dispatch_thread_setspecific(dispatch_queue_key, &_dispatch_main_q);

_dispatch_queue_set_width_init();
}

pthread_key_t

在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构,这样的数据结构称为 线程私有数据(Thread-specific Data,或TSD)
GCD一共定义了4个key:

1
2
3
4
static const unsigned long dispatch_queue_key = __PTK_LIBDISPATCH_KEY0;
static const unsigned long dispatch_sema4_key = __PTK_LIBDISPATCH_KEY1;
static const unsigned long dispatch_cache_key = __PTK_LIBDISPATCH_KEY2;
static const unsigned long dispatch_bcounter_key = __PTK_LIBDISPATCH_KEY3;

pthread_key_create

_dispatch_thread_key_init_np调用pthread_key_create

1
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))

该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。
不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这些值特定于线程并且针对每个线程单独维护。这就相当于提供了一个同名而不同值的全局变量。

pthread_setspecific & pthread_getspecific

这里主线程将自己的TSD设置为_dispatch_main_q函数的地址

1
_dispatch_thread_setspecific(dispatch_queue_key, &_dispatch_main_q);

总结

本文涉及到的基本数据结构只是为了跟大家混个脸熟,实际上为了讲数据结构而讲数据结构,也并非能讲清楚,在后续的GCD源码分析系列中将会逐步阐明。

-------------本文结束 感谢您的阅读-------------

本文标题:GCD源码分析1 —— 开篇

文章作者:lingyun

发布时间:2018年01月31日 - 22:01

最后更新:2018年02月08日 - 23:02

原始链接:https://tsuijunxi.github.io/2018/01/31/GCD源码分析1 —— 开篇/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!

本文标题:GCD源码分析1 —— 开篇

文章作者:lingyun

发布时间:2018年01月31日 - 22:01

最后更新:2018年02月08日 - 23:02

原始链接:https://tsuijunxi.github.io/2018/01/31/GCD源码分析1 —— 开篇/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。